import chalk from 'chalk';
import {DateUtil} from '../../../utils/DateUtil.js';
import {EasySearchClient} from '../../../utils/EasySearchClient.js';
import {buildEasySearchQuery} from '../../../utils/LuceneParser.js';
import {OPSConstant} from "../../../constant/OPSConstant.js";

export const logMixin = {
    async handleLogCommand(rawQuery, rawLookback, options = {}) {
        const luceneQuery = (rawQuery ?? '').trim();
        if (!luceneQuery) {
            throw new Error('log 命令必须提供 Lucene 查询语句，例如 index:"buzz-service" AND reqId:"xxxxxx"');
        }
        const lookback = this.parseLookback(rawLookback);
        const {indexBase, query} = buildEasySearchQuery(luceneQuery);
        const indexName = this.resolveEasySearchIndex(indexBase);
        const size = this.parseLogSize(options.size);
        const follow = Boolean(options.follow);
        if (follow && options.raw) {
            throw new Error('follow 模式暂不支持 --raw 输出，请去掉 --raw 后重试');
        }
        const {start, end} = this.buildTimeRange(lookback.durationMs);
        const client = this.createEasySearchClient(Boolean(options.verbose));
        const timedQuery = this.appendTimeRangeFilter(query, start, end);
        const searchBody = this.buildSearchBody(timedQuery, size, 'desc');
        const searchParams = {
            index: indexName,
            body: searchBody
        };

        this.logger.step(1, `查询 EasySearch 索引: ${indexName}`);
        this.logger.info(
            `时间范围 ${DateUtil.format(start, 'yyyy-MM-dd HH:mm:ss')} ~ ${DateUtil.format(end, 'yyyy-MM-dd HH:mm:ss')}，回溯 ${lookback.label}，最多返回 ${size} 条日志`
        );
        this.logger.info(`EasySearch 请求参数: ${JSON.stringify(searchParams)}`);

        const response = await client.search(searchParams);

        this.printSearchSummary(response, size);
        if (follow) {
            const initialHits = this.extractHits(response);
            await this.streamLog({
                client,
                indexName,
                baseQuery: query,
                size,
                pollInterval: OPSConstant.EASY_SEARCH_FOLLOW_INTERVAL_MS,
                initialStart: start,
                initialHits
            });
            return;
        }
        this.printLogHits(response, Boolean(options.raw));
    },

    parseLookback(rawLookback) {
        const fallback = OPSConstant.EASY_SEARCH_DEFAULT_LOOKBACK;
        const input = (rawLookback ?? fallback).toString().trim();
        const matcher = input.match(/^(\d+)([smhd])$/i);
        if (!matcher) {
            throw new Error('回溯时间格式不正确，请使用 30m、2h、1d 等形式');
        }
        const value = Number.parseInt(matcher[1], 10);
        const unit = matcher[2].toLowerCase();
        if (value <= 0) {
            throw new Error('回溯时间必须为正数');
        }
        const unitMap = {
            s: 1000,
            m: 60 * 1000,
            h: 60 * 60 * 1000,
            d: 24 * 60 * 60 * 1000
        };
        const durationMs = value * unitMap[unit];
        const maxDuration = 7 * 24 * 60 * 60 * 1000;
        if (durationMs > maxDuration) {
            throw new Error('回溯时间过大，请控制在 7 天以内');
        }
        return {
            durationMs,
            label: `${value}${unit}`
        };
    },

    parseLogSize(sizeOption) {
        if (sizeOption === undefined || sizeOption === null) {
            return OPSConstant.EASY_SEARCH_DEFAULT_SIZE;
        }
        const size = Number.parseInt(sizeOption, 10);
        if (!Number.isFinite(size) || size <= 0) {
            throw new Error('日志条数必须为正整数');
        }
        if (size > OPSConstant.EASY_SEARCH_MAX_SIZE) {
            this.logger.warn(`日志条数 ${size} 超出最大限制 ${OPSConstant.EASY_SEARCH_MAX_SIZE}，已自动截断`);
            return OPSConstant.EASY_SEARCH_MAX_SIZE;
        }
        return size;
    },

    buildTimeRange(durationMs) {
        const end = new Date();
        const start = new Date(end.getTime() - durationMs);
        return {start, end};
    },

    resolveEasySearchIndex(indexBase) {
        let base = typeof indexBase === 'string' ? indexBase.trim() : '';
        if (!base) {
            base = this.getFirstConfigValue([
                'easysearch_default_index',
                'easysearch_index',
                'easysearch.app'
            ]) || '';
        }
        if (!base) {
            throw new Error('未找到索引名称，请在查询语句中添加 index:"应用名" 或在全局配置中设置 easysearch_default_index');
        }
        const match = base.match(/^(.*?)-(\d{4}\.\d{2}\.\d{2})$/);
        if (match) {
            base = match[1];
        }
        base = base.replace(/\s+/g, '');
        if (!base) {
            throw new Error('索引名称解析失败，请检查 index 字段');
        }
        const today = DateUtil.format(new Date(), 'yyyy.MM.dd');
        return `${base}-${today}`;
    },

    appendTimeRangeFilter(query, start, end) {
        const rangeClause = {
            range: {
                '@timestamp': {
                    gte: start.toISOString(),
                    lte: end.toISOString(),
                    format: 'strict_date_optional_time'
                }
            }
        };
        if (query?.bool) {
            const cloned = this.cloneBoolQuery(query.bool);
            cloned.filter = cloned.filter || [];
            cloned.filter.push(rangeClause);
            return {bool: cloned};
        }
        return {
            bool: {
                filter: [query, rangeClause]
            }
        };
    },

    buildSearchBody(query, size, order = 'desc', from = 0) {
        return {
            query,
            from: Number.isFinite(from) && from >= 0 ? from : 0,
            size,
            sort: [
                {
                    '@timestamp': {order}
                }
            ],
            highlight: {
                pre_tags: ['@highlighted-field@'],
                post_tags: ['@/highlighted-field@'],
                fields: {
                    '*': {}
                }
            },
            track_total_hits: true
        };
    },

    createEasySearchClient(verbose) {
        const username = this.getFirstConfigValue([
            'easysearch_username',
            'easysearch_userName',
            'easysearch_user',
            'ops_user'
        ]);
        const password = this.getFirstConfigValue([
            'easysearch_password',
            'easysearch_pass',
            'ops_password'
        ]);
        if (!username || !password) {
            throw new Error('未配置 EasySearch 登录信息，请在 ops_global.properties 中补充 easysearch_username 与 easysearch_password');
        }
        return new EasySearchClient(username, password, verbose);
    },

    getFirstConfigValue(keys) {
        for (const key of keys) {
            const value = this.globalOpsFile.read(key);
            if (typeof value === 'string' && value.trim()) {
                return value.trim();
            }
        }
        return null;
    },

    cloneBoolQuery(boolClause) {
        const cloned = {};
        for (const key of ['must', 'filter', 'should', 'must_not']) {
            if (Array.isArray(boolClause[key]) && boolClause[key].length > 0) {
                cloned[key] = [...boolClause[key]];
            }
        }
        if (boolClause.minimum_should_match) {
            cloned.minimum_should_match = boolClause.minimum_should_match;
        }
        return cloned;
    },

    printSearchSummary(response, limit) {
        const took = response?.took ?? 0;
        const hitsTotal = response?.hits?.total?.value ?? 0;
        const hitsRelation = response?.hits?.total?.relation ?? 'eq';
        const returned = Array.isArray(response?.hits?.hits) ? response.hits.hits.length : 0;
        this.logger.success(`查询完成，匹配 ${hitsTotal}${hitsRelation === 'gte' ? '+' : ''} 条，实际返回 ${returned} 条，用时 ${took} ms`);
        if (hitsTotal > limit) {
            this.logger.warn('匹配结果超过限制，请缩小时间范围或增加查询条件');
        }
    },

    printLogHits(response, raw) {
        if (raw) {
            console.log(JSON.stringify(response, null, 2));
            return;
        }
        const hits = this.extractHits(response);
        if (hits.length === 0) {
            this.logger.warn('未查询到符合条件的日志');
            return;
        }
        const sortedHits = this.sortHitsByTimestampAsc(hits);
        this.printHits(sortedHits);
    },

    printHits(hits) {
        hits.forEach((hit, index) => {
            const source = hit?._source ?? {};
            const timestampStr = source['@timestamp'];
            let timestamp = '未知时间';
            if (timestampStr) {
                try {
                    timestamp = DateUtil.format(new Date(timestampStr), 'yyyy-MM-dd HH:mm:ss,SSS');
                } catch (e) {
                    timestamp = timestampStr;
                }
            }
            const level = (source.logLevel || source.level || 'INFO').trim();
            const levelLabel = this.renderLogLevel(level);
            const appName = source.appName || source.app || source.service || '-';
            const thread = source.thread || source.tid || '-';
            const loggerName = this.formatLoggerName(source.logger || source.log || '');
            const reqHighlight = this.renderHighlightSnippet(hit?.highlight?.reqId?.[0]) || source.reqId || source.requestId || '-';
            const message = source.msg || source.message || '';
            const [firstLine, ...restLines] = String(message || '').split('\n');
            const headerSegments = [
                timestamp,
                levelLabel,
                thread ? chalk.magenta(`[${thread}]`) : '',
                reqHighlight ? chalk.green(`[${reqHighlight}]`) : '',
                appName ? chalk.cyan(`[${appName}]`) : '',
                loggerName
            ].filter(Boolean);
            const headerLine = `${headerSegments.join(' ')}${firstLine ? ` - ${firstLine}` : ''}`;
            console.log(headerLine);
            if (restLines.length) {
                restLines.forEach(line => console.log(`    ${line}`));
            }
            const stack = source.stackTrace || source.stacktrace || source.errorStack || '';
            if (stack) {
                console.log(chalk.red('    stack:'));
                this.printMultilineBlock(stack);
            }
        });
    },

    formatLoggerName(loggerName) {
        if (!loggerName) {
            return '';
        }
        const str = String(loggerName);
        const bracketMatch = str.match(/^([A-Za-z_][\w.$]*)\[(\d+)]$/);
        if (bracketMatch) {
            return `${bracketMatch[1]}[${bracketMatch[2]}]`;
        }
        const colonMatch = str.match(/^([A-Za-z_][\w.$]*):(\d+)$/);
        if (colonMatch) {
            return `${colonMatch[1]}[${colonMatch[2]}]`;
        }
        return str;
    },

    renderLogLevel(level) {
        const upper = (level || '').trim().toUpperCase();
        switch (upper) {
            case 'ERROR':
                return chalk.red(`[${upper.padEnd(5, ' ')}]`);
            case 'WARN':
            case 'WARNING':
                return chalk.yellow(`[${'WARN'.padEnd(5, ' ')}]`);
            case 'DEBUG':
                return chalk.blue(`[${upper.padEnd(5, ' ')}]`);
            default:
                return chalk.green(`[${(upper || 'INFO').padEnd(5, ' ')}]`);
        }
    },

    printMultilineBlock(text) {
        String(text).split('\n').forEach(line => {
            console.log(`    ${line}`);
        });
    },

    renderHighlightSnippet(snippet) {
        if (!snippet) {
            return '';
        }
        return snippet.replace(/@highlighted-field@(.*?)@\/highlighted-field@/g, (_, matched) => chalk.yellow(matched));
    },

    extractHits(response) {
        return Array.isArray(response?.hits?.hits) ? response.hits.hits : [];
    },

    sortHitsByTimestampAsc(hits) {
        return [...hits].sort((a, b) => {
            const tsA = this.extractHitTimestamp(a);
            const tsB = this.extractHitTimestamp(b);
            const safeA = tsA ?? Number.MAX_SAFE_INTEGER;
            const safeB = tsB ?? Number.MAX_SAFE_INTEGER;
            return safeA - safeB;
        });
    },

    filterNewHitInfos(hits, lastTimestampMs, boundaryKeys, includeEqual) {
        const result = [];
        for (const hit of hits) {
            const ts = this.extractHitTimestamp(hit);
            if (ts === null) {
                continue;
            }
            if (ts < lastTimestampMs) {
                continue;
            }
            const key = this.buildHitKey(hit, ts);
            if (ts === lastTimestampMs && !includeEqual && boundaryKeys.has(key)) {
                continue;
            }
            result.push({hit, ts, key});
        }
        return result;
    },

    updateStreamState(infos, lastTimestampMs, boundaryKeys) {
        let currentTimestamp = lastTimestampMs;
        let currentKeys = new Set(boundaryKeys);
        for (const info of infos) {
            if (info.ts > currentTimestamp) {
                currentTimestamp = info.ts;
                currentKeys = new Set([info.key]);
            } else if (info.ts === currentTimestamp) {
                currentKeys.add(info.key);
            }
        }
        return {
            lastTimestampMs: currentTimestamp,
            boundaryKeys: currentKeys
        };
    },

    extractHitTimestamp(hit) {
        const timestampStr = hit?._source?.['@timestamp'];
        if (!timestampStr) {
            return null;
        }
        const parsed = Date.parse(timestampStr);
        return Number.isNaN(parsed) ? null : parsed;
    },

    buildHitKey(hit, timestampMs) {
        if (hit?._id) {
            return hit._id;
        }
        const source = hit?._source ?? {};
        const reqId = source.reqId || source.requestId || '';
        const thread = source.thread || source.tid || '';
        const logger = source.logger || source.log || '';
        const message = (source.msg || source.message || '').slice(0, 120);
        return `${timestampMs}-${reqId}-${thread}-${logger}-${message}`;
    },

    async streamLog(params) {
        const {
            client,
            indexName,
            baseQuery,
            size,
            pollInterval,
            initialStart,
            initialHits
        } = params;
        const interval = Number.isFinite(pollInterval) && pollInterval > 0 ? pollInterval : OPSConstant.EASY_SEARCH_FOLLOW_INTERVAL_MS;
        let lastTimestampMs = initialStart.getTime();
        let boundaryKeys = new Set();
        let sequenceCounter = 1;

        const sortedInitialHits = this.sortHitsByTimestampAsc(initialHits || []);
        const initialInfos = this.filterNewHitInfos(sortedInitialHits, lastTimestampMs, boundaryKeys, true);
        if (initialInfos.length > 0) {
            this.printHits(initialInfos.map(info => info.hit), sequenceCounter);
            sequenceCounter += initialInfos.length;
            ({lastTimestampMs, boundaryKeys} = this.updateStreamState(initialInfos, lastTimestampMs, boundaryKeys));
        } else {
            this.logger.warn('暂无历史日志，等待新日志...');
        }

        this.logger.info(`进入监听模式，按 Ctrl+C 退出（刷新间隔 ${Math.round(interval / 1000)}s）`);
        let firstLoop = true;
        while (true) {
            if (firstLoop) {
                firstLoop = false;
            } else {
                await this.sleep(interval);
            }
            const now = new Date();
            const rangeStart = new Date(lastTimestampMs);
            try {
                const nextQuery = this.appendTimeRangeFilter(baseQuery, rangeStart, now);
                const response = await client.search({
                    index: indexName,
                    body: this.buildSearchBody(nextQuery, size, 'asc')
                });
                const hits = this.sortHitsByTimestampAsc(this.extractHits(response));
                const newInfos = this.filterNewHitInfos(hits, lastTimestampMs, boundaryKeys, false);
                if (newInfos.length > 0) {
                    this.printHits(newInfos.map(info => info.hit), sequenceCounter);
                    sequenceCounter += newInfos.length;
                    ({lastTimestampMs, boundaryKeys} = this.updateStreamState(newInfos, lastTimestampMs, boundaryKeys));
                }
            } catch (error) {
                this.logger.warn(`拉取最新日志失败：${error.message}`);
            }
        }
    },

    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    },
};
